using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace KLib.Particles
{
    public class ParticleEmitter : Microsoft.Xna.Framework.DrawableGameComponent
    {
        #region Fields
        private EmitterSettings settings;
        private Particle[] particles;
        private Vector3 position;

        private bool alive;
        private float lifeTime;
        private Effect effect;

        private VertexDeclaration vertexDeclaration;
        private VertexParticle[] particleVerts;
        private Random rand = new Random();
        #endregion

        #region Initialization
        public ParticleEmitter(Game game, EmitterSettings settings, Vector3 position)
            : base(game)
        {
            this.settings = settings;
            this.position = position;
        }

        public override void Initialize()
        {
            particles = new Particle[settings.MaxParticles];
            lifeTime = settings.EmitterLife;
            effect = Game.Content.Load<Effect>("Effects/ParticleEffect");
            effect.CurrentTechnique = effect.Techniques["ScaledPointSprite"];
            effect.Parameters["ParticleTexture"].SetValue(Game.Content.Load<Texture2D>("Textures/particle"));
            base.Initialize();
            vertexDeclaration = new VertexDeclaration(GraphicsDevice, VertexParticle.VertexElements);
            ResetParticles();
        }
        #endregion

        #region Methods
        public override void Update(GameTime gameTime)
        {
            lifeTime -= (float)gameTime.ElapsedGameTime.TotalSeconds;
            if (lifeTime < 0 && settings.EmitterLife != 0)
            {
                alive = false;
                return;
            }

            float radius = 2;

            for (int i = 0; i < particleVerts.Length; i++)
            {
                float angle = particleVerts[i].Data.X;
                float angle2 = particleVerts[i].Data.Y;

                angle += .01f;
                if (angle > 360)
                    angle = 0;
                angle2 += .01f;
                if (angle2 > 360)
                    angle2 = 0;

                float cos = radius * (float)Math.Cos(angle);
                float sin = radius * (float)Math.Sin(angle);
                float cos2 = radius * (float)Math.Cos(angle2);
                float sin2 = (float)Math.Pow(radius, 2) * (float)Math.Sin(angle2);

                particleVerts[i].Position = new Vector3(cos * cos2, sin * cos2, sin2)+Vector3.Up*(float)Math.Sin(gameTime.TotalGameTime.TotalMilliseconds/300);
                //particleVerts[i].Color = new Color(new Vector4(particleVerts[i].Color.ToVector3(), 1f));

                particleVerts[i].Data = new Vector4(angle, angle2, 0, 0);
            }
            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {

            bool pointSpriteEnable = GraphicsDevice.RenderState.PointSpriteEnable;
            bool alphaBlendEnable = GraphicsDevice.RenderState.AlphaBlendEnable;
            bool alphaTestEnable = GraphicsDevice.RenderState.AlphaTestEnable;
            Blend destinationBlend = GraphicsDevice.RenderState.DestinationBlend;
            bool DepthBufferWriteEnable = GraphicsDevice.RenderState.DepthBufferEnable;

            GraphicsDevice.RenderState.PointSpriteEnable = true;
            GraphicsDevice.RenderState.AlphaBlendEnable = true;
            GraphicsDevice.RenderState.AlphaTestEnable = true;
            GraphicsDevice.RenderState.DestinationBlend = Blend.One;
            GraphicsDevice.RenderState.DepthBufferWriteEnable = false;

            GraphicsDevice.VertexDeclaration = vertexDeclaration;
            Matrix wvp = (Matrix.CreateScale(1) * Matrix.CreateFromQuaternion(Quaternion.Identity) * Matrix.CreateTranslation(position)) * Camera.myView * Camera.myProjection;
            effect.Parameters["Projection"].SetValue(Camera.myProjection);
            effect.Parameters["ParticleSize"].SetValue(0.5f);
            effect.Parameters["ViewportHeight"].SetValue(Camera.myViewport.Height);
            effect.Parameters["WorldViewProjection"].SetValue(wvp);

            effect.Begin();
            effect.CurrentTechnique.Passes[0].Begin();

            if (particleVerts.Length >= 15000)
            {
                GraphicsDevice.DrawUserPrimitives<VertexParticle>(PrimitiveType.PointList, particleVerts, 0, particleVerts.Length / 3);
                GraphicsDevice.DrawUserPrimitives<VertexParticle>(PrimitiveType.PointList, particleVerts, particleVerts.Length/3, particleVerts.Length / 3);
                GraphicsDevice.DrawUserPrimitives<VertexParticle>(PrimitiveType.PointList, particleVerts, 2*particleVerts.Length/3, particleVerts.Length / 3);
            }
            else
                GraphicsDevice.DrawUserPrimitives<VertexParticle>(PrimitiveType.PointList, particleVerts, 0, particleVerts.Length);

            effect.CurrentTechnique.Passes[0].End();
            effect.End();

            GraphicsDevice.RenderState.PointSpriteEnable = pointSpriteEnable;
            GraphicsDevice.RenderState.AlphaBlendEnable = alphaBlendEnable;
            GraphicsDevice.RenderState.AlphaTestEnable = alphaTestEnable;
            GraphicsDevice.RenderState.DestinationBlend = destinationBlend;
            GraphicsDevice.RenderState.DepthBufferWriteEnable = DepthBufferWriteEnable;

            base.Draw(gameTime);
        }

        #endregion

        #region Helper Methods
        private void ResetParticles()
        {
            particleVerts = new VertexParticle[settings.MaxParticles];

            for (int i = 0; i < particleVerts.Length; i++)
            {
                particleVerts[i] = new VertexParticle();
                particleVerts[i].Position = position;
                particleVerts[i].Data = new Vector4(rand.Next(0, 360), rand.Next(0, 360), 0, 0);
                if (settings.Color != Color.TransparentBlack)
                {
                    particleVerts[i].Color = settings.Color;
                }
                else
                    particleVerts[i].Color = new Color(new Vector4((float)rand.NextDouble(), (float)rand.NextDouble(), (float)rand.NextDouble(), 1));
            }
        }
        #endregion
    }
}